#include "graphics.hpp"

#include "exception.hpp"

namespace nmc
{
    const float Graphics::pi = 3.1415926535f;
  
    Graphics::Graphics()
    {
  
    }

	Graphics::~Graphics()
	{
		glDeleteTextures(1, &mRenderToTextureHandle);
	}

	void Graphics::createRenderToTexture(int width, int height)
	{
		mRenderToTextureWidth = width;
		mRenderToTextureHeight = height;

		unsigned int* pixels;
		
		int size = (width * height)* 4 * sizeof(unsigned int);
		pixels = (unsigned int*)new GLuint[size];
		
		for (int i = 0; i < size; i++)
		{
			pixels[i] = 0;
		}

		glGenTextures(1, &mRenderToTextureHandle);
		glBindTexture(GL_TEXTURE_2D, mRenderToTextureHandle);

		glTexImage2D(GL_TEXTURE_2D,
                     0,
                     4,
                     width,
                     height,
                     0,
                     GL_RGBA,
                     GL_UNSIGNED_BYTE,
                     pixels);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

		delete [] pixels;	
	}

	void Graphics::beginRenderToTexture()
	{
		glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity(); 

		glViewport(0, 0, mWidth ,mHeight);
		glOrtho(0, mWidth, 0, mHeight, -1, 1);
	}

	void Graphics::endRenderToTexture()
	{
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, mRenderToTextureHandle);
		glCopyTexImage2D(GL_TEXTURE_2D, 
			    	     0, 
						 GL_RGBA, 
						 0, 
						 0, 
						 mRenderToTextureWidth, 
						 mRenderToTextureHeight, 
						 0);
		glDisable(GL_TEXTURE_2D);
		glMatrixMode(GL_PROJECTION);
        glPopMatrix();
	}

	void Graphics::drawRenderToTexture(int x, int y, int width, int height)
	{
		setColor(Color(0xffffff));

		glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();

		const gcn::ClipRectangle& top = mClipStack.top();

        int dstX = 0;
        int dstY = 0;

        float texX1 = x / (float)mRenderToTextureWidth;
        float texY1 = y / (float)mRenderToTextureHeight;
        float texX2 = (x+width) / (float)mRenderToTextureWidth;
        float texY2 = (y+height) / (float)mRenderToTextureHeight;

		texX2 = texX2 / 2.0f;
		texY2 = texY2 / 2.0f;

		/*
		float texX1 = 0.0f;
        float texY1 = 0.0f;
        float texX2 = 1.0f;
        float texY2 = 1.0f;
		*/

		glEnable(GL_TEXTURE_2D);
        
		glBindTexture(GL_TEXTURE_2D, mRenderToTextureHandle);
        glBegin(GL_QUADS);
        glTexCoord2f(texX1, texY1);
        glVertex3i(dstX, dstY, 0);

        glTexCoord2f(texX1, texY2);
        glVertex3i(dstX, dstY + height, 0);

        glTexCoord2f(texX2, texY2);
        glVertex3i(dstX + width, dstY + height, 0);

        glTexCoord2f(texX2, texY1);
        glVertex3i(dstX + width, dstY, 0);
        glEnd();

		glDisable(GL_TEXTURE_2D);

		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
	}

    void Graphics::setBlendMode(BlendMode mode)
    {
        switch(mode)
        {
          case ADD:
              glBlendFunc(GL_SRC_ALPHA, GL_ONE);
              break;
          case MULTIPLY:
              glBlendFunc(GL_DST_COLOR, GL_ZERO);
              break;
          case ALPHA:
              glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
              break;
          default:
              throw NMC_EXCEPTION("Unknown blend mode.");
        }

        mBlendMode = mode;
    }
    
    
	Graphics::BlendMode Graphics::getBlendMode()
    {
		return mBlendMode;
    }    

    void Graphics::rotate(int x, int y, float angle)
    {
        glTranslatef((GLfloat)x, (GLfloat)y, 0.0f);
        glRotatef((GLfloat)((-180*angle)/pi), 0.0f, 0.0f, 1.0f); 
        glTranslatef((GLfloat)-x, (GLfloat)-y, 0.0f);
    }
    
    void Graphics::scale(int x, int y, float xAmount, float yAmount)
    {
        glTranslatef((GLfloat)x, (GLfloat)y, 0.0f);
        glScalef(xAmount, yAmount, 1.0f);
        glTranslatef((GLfloat)-x, (GLfloat)-y, 0.0f);
    }
    
    void Graphics::translate(int xAmount, int yAmount)
    {
        glTranslatef((GLfloat)xAmount, (GLfloat)yAmount, 0.0f);
    }
 
    void Graphics::pushMatrix()
    {
        glPushMatrix();
    }
    
    void Graphics::popMatrix()
    {
        glPopMatrix();
    }

	void Graphics::_beginDraw()
    {
        glPushAttrib(GL_COLOR_BUFFER_BIT |
					 GL_CURRENT_BIT |
					 GL_DEPTH_BUFFER_BIT |
					 GL_ENABLE_BIT |
					 GL_FOG_BIT |
					 GL_LIGHTING_BIT |
					 GL_LINE_BIT |
					 GL_POINT_BIT |
					 GL_POLYGON_BIT |
					 GL_SCISSOR_BIT |
					 GL_STENCIL_BUFFER_BIT |
					 GL_TEXTURE_BIT |
					 GL_TRANSFORM_BIT);

        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glLoadIdentity();
		// This takes care of some wierd OpenGL offsets
		// when using 2D projection.
		glTranslatef(0.375,0.375,0.0);

        glMatrixMode(GL_TEXTURE);
        glPushMatrix();
        glLoadIdentity();

		glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity(); 

		glViewport(0, 0, mWidth ,mHeight);
		glOrtho(0, mWidth, mHeight, 0, -1, 1);
		glPointSize(1.0);
		glLineWidth(1.0);

		pushClipArea(Rectangle(0, 0, mWidth, mHeight));

		// Disable everything we don't need.
        glDisable(GL_LIGHTING);
        glDisable(GL_CULL_FACE);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_TEXTURE_2D);

		// Enable everything we do need.
        glEnable(GL_SCISSOR_TEST);

        // We want color to affect images.
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		// Standard blending is alpha.
        setBlendMode(ALPHA);
    }

 
    void Graphics::setColor(const gcn::Color& color)
    {
		mColor = color;
        glColor4f(color.r/255.0,
                  color.g/255.0,
                  color.b/255.0,
                  color.a/255.0);

		mAlpha = true;
        glEnable(GL_BLEND);
    }
}
